﻿using NUnit.Framework;
using Tycho.Parser.Peg;

namespace Tycho.Parser.Tests.Peg {
    [TestFixture]
    public class IdentifierProductionSpecification {
        [Test]
        public void ShouldReturnNewIdentifierTermAndIncrementIndexIfIdentifierToken () {
            var id = IdentifierProduction.CreateTerminal ();

            var source = "a b c";

            var result = id.Parse (source);

            Assert.AreEqual (1, result.Index);
            Assert.IsTrue (result.Term is IdentifierTerm);
            var identifier = (IdentifierTerm) result.Term;
            Assert.AreEqual ("a", identifier.Name);
            Assert.AreEqual (0, identifier.SourceInformation.Index);
            Assert.AreEqual (1, identifier.SourceInformation.Length);
        }

        private static void AssertParseIdentifier (string sourceString) {
            ParseResult result = ParseIdentifier (sourceString);

            Assert.IsNotNull (result);
            Assert.AreEqual (sourceString.Length, result.Index);
            Assert.IsTrue (result.Term is IdentifierTerm);
            Assert.AreEqual (sourceString, ((IdentifierTerm) result.Term).Name);
        }

        private static void AssertPartiallyParseIdentifier (string expectedIdentifier, string sourceString) {
            ParseResult result = ParseIdentifier (sourceString);

            Assert.IsNotNull (result);
            Assert.AreEqual (expectedIdentifier.Length, result.Index);
            Assert.IsTrue (result.Term is IdentifierTerm);
            var identifier = (IdentifierTerm) result.Term;
            Assert.AreEqual (expectedIdentifier, identifier.Name);
            Assert.AreEqual (0, identifier.SourceInformation.Index);
            Assert.AreEqual (expectedIdentifier.Length, identifier.SourceInformation.Length);
        }

        private static void AssertNotParseIdentifier (string sourceString) {
            ParseResult result = ParseIdentifier (sourceString);

            Assert.IsNull (result);
        }

        private static ParseResult ParseIdentifier (string sourceString) {
            var id = IdentifierProduction.CreateTerminal ();

            var source = sourceString;

            return id.Parse (source);
        }

        [Test]
        public void ShouldParseValidIdentifiers () {
            AssertParseIdentifier ("abcd");
            AssertParseIdentifier ("_abcd");
            AssertParseIdentifier ("a_bcd");
            AssertParseIdentifier ("a-bcd");
            AssertParseIdentifier ("a9a");
            AssertParseIdentifier ("a-9");
            AssertParseIdentifier ("a-_-a");
        }

        [Test]
        public void ShouldFailOnInvalidIdentifiers () {
            AssertNotParseIdentifier ("9a");
            AssertNotParseIdentifier ("-a");
            AssertNotParseIdentifier ("");
        }

        [Test]
        public void ShouldPartiallyParseIdentifiers () {
            AssertPartiallyParseIdentifier ("abc", "abc{");
            AssertPartiallyParseIdentifier ("abc", "abc+");
            AssertPartiallyParseIdentifier ("abc", "abc ");
            AssertPartiallyParseIdentifier ("abc", "abc\n");
        }

        [Test]
        public void ShouldReturnNullAndNotIncrementIndexIfNotIdentifierToken () {
            var id = IdentifierProduction.CreateTerminal ();

            var source = "8 b c";

            var result = id.Parse (source, 0, null);
            Assert.IsNull (result);
        }

        [Test]
        public void ShouldReturnNullIfIndexIsOutOfTokenArray () {
            var id = IdentifierProduction.CreateTerminal ();

            var source = "a b c";

            var result = id.Parse (source, 5, null);
            Assert.IsNull (result);
        }

        [Test]
        public void NameIsIdentifier () {
            IProduction id = IdentifierProduction.CreateTerminal ();
            Assert.That (id.Name, Is.EqualTo ("identifier"));
        }
    }
}